/**
 * 
 */
package lt.vin.ht.server.servlet;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import lt.vin.ht.server.dao.SchemaDao;

import org.w3c.dom.Document;
import org.w3c.tidy.Tidy;
import org.xml.sax.SAXException;

import com.google.inject.Inject;

/**
 * This class transforms html from provided url using specified xslt transformation
 * @author Vincentas
 * 
 */
public class HtmlTransformer {
	
	@Inject
	private SchemaDao schemaDao;
	
	@Inject
	private Tidy tidyService;
	
	/*
	 * Workaround : http://code.google.com/p/googleappengine/issues/detail?id=1452
	 */	
	@Inject
	private TransformerFactory transformerFactory;
	
	@Inject
	private SchemaFactory schemaFactory;
		
	public String transform(String url, String xsltTransformation) throws IOException, TransformerException {
		return transform(url, xsltTransformation, true);
	}
	
	public String transform(String url, String xsltTransformation, boolean format) throws IOException, TransformerException {
		StringWriter writer = new StringWriter();			
		
		Transformer transformer = getTransformerFactory().newTransformer(new StreamSource(new StringReader(xsltTransformation)));
		transformer.transform(new DOMSource(getDocument(url)), new StreamResult(writer));

		String result = writer.toString();
		if (format) {
			result = format(result);
		}
		
		return result; 
	}
	
	public String transform(String url, String xsltTransformation, String xmlSchema) throws IOException, TransformerException, SAXException {
		return transform(url, xsltTransformation, xmlSchema, true);
	}
	
	public String transform(String url, String xsltTransformation, String xmlSchema, boolean format) throws IOException, TransformerException, SAXException {
		String xml = transform(url, xsltTransformation);
		
		if (xmlSchema != null) {
			validate(xml, xmlSchema);
		}
		
		return xml;
	}
	
	private Document getDocument(String url) throws MalformedURLException, IOException {
		URLConnection connection = new URL(url).openConnection();
		connection.setConnectTimeout(10000);
		connection.setReadTimeout(30000);
		
		return getTidyService().parseDOM(connection.getInputStream(), null);
	}

	private String format(String xml) throws TransformerException {
		Transformer transformer = getTransformerFactory().newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
		
		StringWriter writer = new StringWriter();
		transformer.transform(new StreamSource(new StringReader(xml)), new StreamResult(writer));		
		
		return writer.toString();
	}
	
	private void validate(String xml, String xmlSchema) throws SAXException, IOException {        
        Schema schema = getSchemaFactory().newSchema(new StreamSource(new StringReader(xmlSchema)));
        
       	schema.newValidator().validate(new StreamSource(new StringReader(xml)));
	}

	public Tidy getTidyService() {
		return tidyService;
	}

	public void setTidyService(Tidy tidyService) {
		this.tidyService = tidyService;
	}

	public SchemaDao getSchemaDao() {
		return schemaDao;
	}

	public void setSchemaDao(SchemaDao schemaDao) {
		this.schemaDao = schemaDao;
	}

	public TransformerFactory getTransformerFactory() {
		return transformerFactory;
	}

	public void setTransformerFactory(TransformerFactory transformerFactory) {
		this.transformerFactory = transformerFactory;
	}

	public SchemaFactory getSchemaFactory() {
		return schemaFactory;
	}

	public void setSchemaFactory(SchemaFactory schemaFactory) {
		this.schemaFactory = schemaFactory;
	}
}
